﻿using LibNiconico.NiconicoApi.Alert;
using LibNiconico.NiconicoApi.Exceptions;
using LibNiconico.NiconicoApi.Utils;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;


namespace LibNiconico.NiconicoApi.Internal.Http
{
    internal class NiconicoHttpClient : INiconicoHttpClient
    {

        private static async Task<Stream> getRequestStreamAsync(HttpWebRequest request)
        {

            return await Task.Factory.FromAsync<Stream>(request.BeginGetRequestStream, request.EndGetRequestStream, null).ConfigureAwait(false);


        }

        private static async Task<HttpWebResponse> getResponseStreamAsync(HttpWebRequest request)
        {
            
            return (HttpWebResponse)await Task.Factory.FromAsync<WebResponse>(request.BeginGetResponse, request.EndGetResponse, null).ConfigureAwait(false);



        }

        public CookieContainer CookieContainer
        {
            get;
            protected set;
        }



        public ICollection<KeyValuePair<string, string>> Headers
        {
            get;
            protected set;
        }


        

        /// <summary>
        /// コンストラクタ
        /// </summary>
        protected NiconicoHttpClient() 
        {
            CookieContainer = new CookieContainer();
            Headers = new List<KeyValuePair<string,string>>();
        }

        public void AddHeader(string name, string value)
        {
            Headers.Add(new KeyValuePair<string, string>(name, value));

        }

        protected virtual void BeforeRequest(Uri url) { }

        /// <summary>
        /// 非同期にリクエストを実行
        /// </summary>
        /// <param name="url"></param>
        /// <param name="func"></param>
        /// <returns></returns>
        private  async Task<HttpWebResponse> requestAsync(Uri url, Func<HttpWebRequest, Task<WebResponse>> func)
        {
            try
            {
                BeforeRequest(url);
                HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);

#if !NETFX_CORE
                req.UserAgent = user_agent;
#endif
                req.CookieContainer = CookieContainer;
                return (HttpWebResponse)await func(req).ConfigureAwait(false);
            }
            catch (Exception e)
            {
                throw new NicoApiException(e.Message, e);
            }

        }

        private async Task<HttpWebResponse> postAsync(Uri uri, NicoPostAble content)
        {
            return await requestAsync(uri, async (req) =>
            {
                req.Method = "POST";
                req.ContentType = content.ContentType;
                var bytes = content.AsPostData();


                using (Stream stream = await getRequestStreamAsync(req).ConfigureAwait(false))
                {
                    await stream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
                    await stream.FlushAsync().ConfigureAwait(false);
                    
                }

                return await getResponseStreamAsync(req).ConfigureAwait(false);
            }).ConfigureAwait(false);
        }

        public async Task<NiconicoHttpResponse<Stream>> GetStreamAsync(Uri uri)
        {

            try
            {
                var response = await getAsync(uri).ConfigureAwait(false);
                return new NiconicoHttpResponse<Stream>(response.GetResponseStream(), response);
            }
            catch (NicoApiException e)
            {
                throw e;
            }
            catch (Exception e)
            {
                throw NicoApiException.CreateHttpException(e);
            }


        }

        public async Task PostAsync(Uri uri, NicoPostAble content)
        {
            await this.postAsync(uri, content).ConfigureAwait(false);
        }

        public async Task<NiconicoHttpResponse<string>> GetStringAsync(Uri uri)
        {
            var response = await GetStreamAsync(uri).ConfigureAwait(false);
            return NiconicoHttpResponse.ConvertContent(await ReadStream(response.Content).ConfigureAwait(false),response);

        }

        public async Task<NiconicoHttpResponse<CookieContainer>> GetCookieAsync(Uri uri)
        {
            var response = await this.getAsync(uri).ConfigureAwait(false);
            return new NiconicoHttpResponse<CookieContainer>( this.CookieContainer,response);
        }

        public async Task<NiconicoHttpResponse<CookieContainer>> PostCookieAsync(Uri uri, NicoPostAble content)
        {
            var response = await this.postAsync(uri, content).ConfigureAwait(false);
            return new NiconicoHttpResponse<CookieContainer>(this.CookieContainer, response);
        }

        public async Task<NiconicoHttpResponse<string>> PostStringAsync(Uri uri, NicoPostAble content)
        {
            var response = await PostStreamAsync(uri, content).ConfigureAwait(false);
            return  NiconicoHttpResponse.ConvertContent(await ReadStream(response.Content).ConfigureAwait(false),response);
        }

        public async Task<NiconicoHttpResponse<Stream>> PostStreamAsync(Uri uri, NicoPostAble content)
        {
            try
            {
                var response = await postAsync(uri, content).ConfigureAwait(false);
                return new NiconicoHttpResponse<Stream>(response.GetResponseStream(),response);
            }
            catch (NicoApiException e)
            {
                throw new NicoApiException(e.Message, e);
            }
            catch (Exception e)
            {
                throw NicoApiException.CreateHttpException(e);
            }

        }

        private async Task<string> ReadStream(Stream stream)
        {
            try
            {
                StreamReader reader = new StreamReader(stream);
                return await reader.ReadToEndAsync().ConfigureAwait(false);
            }
            catch (NicoApiException e)
            {
                throw new NicoApiException(e.Message, e);
            }
            catch (Exception e)
            {
                throw NicoApiException.CreateHttpException(e);
            }

        }

        private async Task<HttpWebResponse> getAsync(Uri uri)
        {
            return await requestAsync(uri, async (req) =>
            {

                return await getResponseStreamAsync(req).ConfigureAwait(false);

            }).ConfigureAwait(false);
        }


        public void SetCookie(Uri uri, Cookie cookie)
        {
            this.CookieContainer.Add(uri, cookie);
        }

        public void SetCookies(Uri uri, CookieCollection cookies)
        {
            this.CookieContainer.Add(uri, cookies);
        }



        /// <summary>
        /// このオブジェクトに割り当てられたリソースを解放
        /// </summary>
        public void Dispose()
        {
        }

        internal static INiconicoHttpClient CreateNiconicoApi()
        {
            return CreateNiconicoApi(new NiconicoApiInfoGetCreateInfo());
        }

        internal static INiconicoHttpClient CreateNiconicoApi(NiconicoApiInfoGetCreateInfo createInfo)
        {
            return NiconicoApiHttpClient.Create(createInfo);
            
        }

        internal static INiconicoHttpClient CreateNiconicoApi(NiconicoAuth auth, NiconicoApiInfoGetCreateInfo createInfo)
        {
            return MostLoginNiconicoApiHttpClient.Create(auth ,createInfo);
        }


        internal static INiconicoHttpClient CreateNiconicoApi(NiconicoAuth auth)
        {
            return MostLoginNiconicoApiHttpClient.Create(auth, new NiconicoApiInfoGetCreateInfo());
        }


        internal static INiconicoHttpClient CreateNiconicoAlertApi()
        {
            return NiconicoAlertApiHttpClient.Create();
        }


        internal static void SetUserAgent(string applicationName)
        {
            user_agent = createUserAgent(applicationName);
        }

        private static string createUserAgent(string applicationName)
        {
            if (string.IsNullOrEmpty(applicationName))
            {
                throw new ArgumentNullException();
            }

            return applicationName + "(LibNiconico/NiconicoApiHelper(1.0.0.0)";
        }

        private static string user_agent = createUserAgent("Unknown");



    }
}
